﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NeturalMath.Properties;
using NeturalMath.Units;

namespace NeturalMath
{
    /// <summary>
    /// Data type for representing string values to NeturalMath
    /// </summary>
    public sealed class StringValue:MathValue
    {
        #region Constructor

        internal StringValue(string value,MathRuntime runtime)
            :base(ValueTypes.String,"string",value,runtime)
        {
            Value = value;
        }

        /// <summary>
        /// Overrides the base Value property with a string value
        /// </summary>
        public new string Value { get; private set; }

        #endregion

        #region Basic Math Functions

        /// <summary>
        /// Flips the bits of the string.
        /// </summary>
        /// <returns></returns>
        protected override MathValue Flip()
        {
            var newSet = new char[Value.Length];
            for(var i=0;i<Value.Length;i++)
            {
                newSet[i] = Value[i].Flip();
            }
            return Runtime.NewString(new String(newSet));
        }

        /// <summary>
        /// Overrides basic Add functionality.
        /// </summary>
        /// <param name="arg">The value to be added</param>
        /// <returns>The Sum of the two values</returns>
        protected override MathValue Add(MathValue arg)
        {
            var other = (StringValue)arg;
            return Runtime.NewString(Value + other.Value);
        }

        /// <summary>
        /// Overrides basic Subtract functionality.
        /// </summary>
        /// <param name="arg">The value to be subtracted</param>
        /// <returns>The difference between the two values</returns>
        protected override MathValue Subtract(MathValue arg)
        {
            var other = (StringValue)arg;
            return Runtime.NewString(Value.Replace(other.Value,string.Empty));
        }

        /// <summary>
        /// Overrides basic Multiply functionality.
        /// </summary>
        /// <param name="arg">The value to be multiplied</param>
        /// <returns>The product between the two values</returns>
        protected override MathValue Multiply(MathValue arg)
        {
            var number = arg.ConvertToNumber();
            var result = new StringBuilder();
            for (int i = 1; i <= (double)number.Value; i++)
                result.Append(Value);
            
            return Runtime.NewString(result.ToString());
        }

        /// <summary>
        /// Overrides basic Divide functionality.
        /// </summary>
        /// <param name="arg">The value to be divided by</param>
        /// <returns>The dividand between the two values</returns>
        protected override MathValue Divide(MathValue arg)
        {
            var s = arg.ToString();
            var ex = new Regex(s);
            var matches = ex.Matches(Value);
            return Runtime.NewNumber(matches.Count);
        }

        /// <summary>
        /// Overrides basic Modulo functionality.
        /// </summary>
        /// <param name="arg">The value to be divided by</param>
        /// <returns>The remainder between the two values</returns>
        protected override MathValue Modulo(MathValue arg)
        {
            var s = arg.ToString();
            var array = Value.Split(new[] { s }, StringSplitOptions.None);
            return Runtime.NewSet(array.Select(Runtime.NewString));
        }

        /// <summary>
        /// Overrides basic Power functionality.
        /// </summary>
        /// <param name="arg">The value to be raised by</param>
        /// <returns>The exponent of the two values</returns>
        protected override MathValue Power(MathValue arg)
        {
            throw new MathException(ErrorCodes.OperatorNotSupported, Resources.StringsDoNotSupportExponents);
        }

        protected override MathValue MinusMinus()
        {
            return Runtime.NewString(Value.Substring(0, Value.Length - 1));
        }

        protected override MathValue PlusPlus()
        {
            return Runtime.NewString(Value + " ");
        }

        #endregion

        #region Comparision Functions

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsLessThan(MathValue arg)
        {
            var other = (string)arg.Value;
            for (int i=0;i<Value.Length;i++)
            {
                if (i > other.Length - 1)
                    return false;
                var cThis = Value[i];
                var cOther = other[i];

                if (cThis < cOther)
                    return true;
                else if (cThis > cOther)
                    return false;
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsGreaterThan(MathValue arg)
        {
            var other = (string)arg.Value;
            for (int i = 0; i < Value.Length; i++)
            {
                if (i > other.Length - 1)
                    return true;

                var cThis = Value[i];
                var cOther = other[i];

                if (cThis < cOther)
                    return false;
                else if (cThis > cOther)
                    return true;
            }
            return false;
        }

        #endregion

        #region Conversion Functions

        /// <summary>
        /// Converts the current value to a string
        /// </summary>
        /// <returns>A string representation of the value</returns>
        protected internal override MathValue ConvertToString()
        {
            return this;
        }

        /// <summary>
        /// Converts the value to a number
        /// </summary>
        /// <returns>The numeric data for the value</returns>
        protected internal override MathValue ConvertToNumber()
        {
            if (string.Equals(Value, (string)Runtime.UndefinedString.Value, StringComparison.CurrentCultureIgnoreCase))
                return Runtime.Undefined;

            if (string.Equals(Value, (string)Runtime.InfinityString.Value, StringComparison.CurrentCultureIgnoreCase))
                return Runtime.Infinity;

            if (string.Equals(Value, (string)Runtime.NegativeInfinityString.Value, StringComparison.CurrentCultureIgnoreCase))
                return Runtime.NegativeInfinity;

            double number;

            var result = double.TryParse(Value,out number);
            if (!result)
                throw new CastException(ErrorCodes.StringCouldNotConvert, string.Format(Resources.StringCouldNotConvert,Value));
            return Runtime.NewNumber(number);
        }

        /// <summary>
        /// Converts the value to a boolean
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToBoolean()
        {
            bool b;
            var result = bool.TryParse(Value,out b);
            if (!result)
                throw new CastException(ErrorCodes.StringCouldNotConvert, Resources.StringCouldNotConvert);
            return  Runtime.NewBool(b);
        }

        /// <summary>
        /// Converts the value to a range
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToRange()
        {
            var output = MathRuntime.DefaultRuntime.ExecuteText(Value);
            if (!(output is RangeValue))
                throw new CastException(ErrorCodes.StringCouldNotConvert, Resources.StringCouldNotConvert);
            return output;
        }

        /// <summary>
        /// Converts the value to a complex
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToComplex()
        {
            var output = MathRuntime.DefaultRuntime.ExecuteText(Value);
            if (!(output is ComplexValue))
                throw new CastException(ErrorCodes.StringCouldNotConvert, Resources.StringCouldNotConvert);
            return output;
        }

        /// <summary>
        /// Converts the value to a unit, based on the specified unit measure
        /// </summary>
        /// <param name="measure"></param>
        /// <returns></returns>
        protected internal override MathValue ConvertToUnit(Units.UnitMeasure measure)
        {
            var output = MathRuntime.DefaultRuntime.ExecuteText(Value);
            if (!(output is UnitValue))
                throw new CastException(ErrorCodes.StringCouldNotConvert, Resources.StringCouldNotConvert);
            return output;
        }

        /// <summary>
        /// Converts the provided value into a StringValue
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected override MathValue ConvertToNativeValue(MathValue value)
        {
            return value.ConvertToString();
        }

        /// <summary>
        /// Overrides base IsTrue function
        /// </summary>
        /// <returns></returns>
        protected override bool IsTrue()
        {
            return (bool)ConvertToBoolean().Value;
        }

        /// <summary>
        /// Overrides base ToMinus function
        /// </summary>
        /// <returns></returns>
        protected override MathValue ToMinus()
        {
            return Runtime.NewString(new String(Value.Reverse().ToArray()));
        }

        #endregion

        /// <summary>
        /// Returns a string representation of the data surrounded by single quotes
        /// </summary>
        /// <returns>A copy of the string data</returns>
        protected override string GetToString()
        {
            return string.Format("'{0}'",Value);
        }

        #region Logical Functions

        /// <summary>
        /// Overrides base and function
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue And(MathValue arg)
        {
            var other = arg.ConvertToString();
            var intersection = Value.Intersect((string) other.Value).ToArray();
            return Runtime.NewString(new String(intersection));
        }

        /// <summary>
        /// Overrides base or function
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Or(MathValue arg)
        {
            var other = arg.ConvertToString();
            var union = Value.Union((string)other.Value).ToArray();
            return Runtime.NewString(new String(union));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Xor(MathValue arg)
        {
            var or = (string)Or(arg).Value;
            var and = (string)And(arg).Value;
            var except = or.Except(and).ToArray();
            return Runtime.NewString(new String(except));
        }

        #endregion


        
    }
}
